package com.lemon.conf.redis.custom;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

/**
 * 缓存拦截，用于注册方法信息
 */
@Aspect
@Component
public class CachingAnnotationsAspect {

	private static final Logger logger = LoggerFactory.getLogger(CachingAnnotationsAspect.class);

	@Autowired
	private InvocationRegistry cacheRefreshSupport;

	/**
	 * <b>Description:<em><blockquote>
	 * 通过反射机制获取执行方法注解的内容信息
	 * </blockquote></em></b>
	 * 
	 * @Title: getMethodAnnotations
	 * @param <T>
	 * @param ae
	 * @param annotationType
	 * @return
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	private <T extends Annotation> List<T> getMethodAnnotations(AnnotatedElement ae, Class<T> annotationType) {
		List<T> anns = new ArrayList<T>(2);
		// look for raw annotation
		T ann = ae.getAnnotation(annotationType);
		if (ann != null) {
			anns.add(ann);
		}
		// look for meta-annotations
		for (Annotation metaAnn : ae.getAnnotations()) {
			ann = metaAnn.annotationType().getAnnotation(annotationType);
			if (ann != null) {
				anns.add(ann);
			}
		}
		return (anns.isEmpty() ? null : anns);
	}

	/**
	 * <b>Description:<em><blockquote>
	 * 获取被被通知方法参数, 包括方法名,注解内容,返回值
	 * </blockquote></em></b>
	 * 
	 * @Title: getSpecificmethod
	 * @param pjp
	 * @return
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	private Method getSpecificmethod(ProceedingJoinPoint pjp) {
		MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
		Method method = methodSignature.getMethod();
		// The method may be on an interface, but we need attributes from the
		// target class. If the target class is null, the method will be
		// unchanged.
		Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget());
		if (targetClass == null && pjp.getTarget() != null) {
			targetClass = pjp.getTarget().getClass();
		}
		Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
		// If we are dealing with method with generic parameters, find the
		// original method.
		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
		return specificMethod;
	}

	// @Pointcut()
	// public void serviceImp() {
	// }

	@Pointcut("@annotation(org.springframework.cache.annotation.Cacheable)")
	public void pointcut() {
	}

	/**
	 * <b>Description:<em><blockquote>
	 * 拦截所有redis里面不存在的cachename 的请求
	 * <br>
	 * 然后将请求里面的信息封装到cachedinvocation.java中 , 方便后面获取以及刷新执行方法
	 * </blockquote></em></b>
	 * 
	 * @Title: registerInvocation
	 * @param joinPoint
	 * @return
	 * @throws Throwable
	 * @author Ran_cc
	 * @date 2018年5月29日
	 */
	@Around("pointcut()")
	public Object registerInvocation(ProceedingJoinPoint joinPoint) throws Throwable{

		Method method = this.getSpecificmethod(joinPoint);
		List<Cacheable> annotations=this.getMethodAnnotations(method,Cacheable.class);
		String keyName = "";
		Set<String> cacheSet = new HashSet<String>();
		for (Cacheable cacheables : annotations) {
			//这里要特别注意.  在注解中 cacheNames 和 value 的性质一样
			//所以这里要两个都获取一下. 以免获取不到cachename
			String [] value = cacheables.cacheNames();
			if ( value.length <= 0 ) {
				value = cacheables.value();
			}
			List<String> asList = Arrays.asList(value);
			//String key = cacheables.key();
			keyName = cacheables.keyGenerator();

			cacheSet.addAll(asList);
		}
		cacheRefreshSupport.registerInvocation(joinPoint.getTarget(), method, joinPoint.getArgs(), cacheSet, keyName);
		return joinPoint.proceed();

	}


}